﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;

namespace Kyoh.Lib.Component
{
    /// <summary>列挙型のメンバをバインドする <see cref="IBindingList"/> インターフェースの実装を定義します。</summary>
    public class EnumBindingSource : System.ComponentModel.Component, ITypedList, IListSource
    {
        //private readonly BindingListImpl<KeyValuePair<string, Enum>> _enumList = new BindingListImpl<KeyValuePair<string, Enum>>();
        private object _enumList = new List<KeyValuePair<string, Enum>>();
        private Type _enumType;

        [NonSerialized]
        private PropertyDescriptorCollection _properties;

        /// <summary>列挙対象の列挙型の具体的な型情報を取得・設定します。</summary>
        [TypeConverter(typeof(TypeNameConverter))]
        public Type EnumType
        {
            get { return _enumType; }
            set
            {
                if (value == null || DesignMode)
                {
                    //_enumList.Clear();
                    _enumList = new List<KeyValuePair<string, Enum>>();
                    _enumType = value;
                }
                else
                {
                    if (!value.IsSubclassOf(typeof(Enum)))
                        throw new InvalidOperationException();

                    var converter = GetTypeConverter(value);

                    var pairType = typeof(KeyValuePair<,>).MakeGenericType(typeof(string), value);
                    var listType = typeof(BindingListImpl<>).MakeGenericType(pairType);
                    var sortType = typeof(List<>).MakeGenericType(pairType);
                    var compType = typeof(GenericNameValuePairComparer<>).MakeGenericType(value);

                    _properties = TypeDescriptor.GetProperties(pairType);

                    //_enumList.Clear();
                    var pairConst = pairType.GetConstructor(new[] { typeof(string), value });
                    var listConst = listType.GetConstructor(Type.EmptyTypes);
                    var sortConst = sortType.GetConstructor(Type.EmptyTypes);
                    var compConst = compType.GetConstructor(Type.EmptyTypes);

                    var listAdd = listType.GetMethod("Add", new[] { pairType });
                    var sortAdd = sortType.GetMethod("Add", new[] { pairType });

                    var sortSort = sortType.GetMethod("Sort", new[] { compType });
                    var list = (IEnumerable)sortConst.Invoke(new object[] { });
                    if (converter != null)
                        foreach (Enum enumValue in Enum.GetValues(value))
                            //list.Add(new KeyValuePair<string, Enum>(converter.ConvertToString(enumValue), enumValue));
                            sortAdd.Invoke(list,
                                           new[]
                                               {
                                                   pairConst.Invoke(new object[]
                                                                        {
                                                                            converter.ConvertToString(enumValue),
                                                                            enumValue
                                                                        })
                                               });
                    else
                        foreach (Enum enumValue in Enum.GetValues(value))
                            //list.Add(new KeyValuePair<string, Enum>(enumValue.ToString(CultureInfo.InvariantCulture), enumValue));
                            sortAdd.Invoke(list,
                                           new[]
                                               {
                                                   pairConst.Invoke(new object[]
                                                                        {
                                                                            enumValue.ToString(),
                                                                            enumValue
                                                                        })
                                               });

                    //list.Sort(Comparer);
                    var compare = compConst.Invoke(new object[] { });
                    sortSort.Invoke(list, new[] { compare });

                    _enumList = listConst.Invoke(new object[] { });
                    foreach (var v in list)
                        //_enumList.Add(v);
                        listAdd.Invoke(_enumList, new[] { v });

                    _enumType = value;
                }
            }
        }

        private static EnumConverter GetTypeConverter(Type enumType)
        {
            foreach (TypeConverterAttribute attr in enumType.GetCustomAttributes(typeof(TypeConverterAttribute), true))
            {
                try
                {
                    var typeConverterType = Type.GetType(attr.ConverterTypeName);
                    if (typeConverterType == null || !typeConverterType.IsSubclassOf(typeof(EnumConverter)))
                        continue;
                    var constructorInfo = typeConverterType.GetConstructor(new[] { typeof(Type) });
                    if (constructorInfo != null)
                        return (EnumConverter)constructorInfo.Invoke(new object[] { enumType });
                }
                catch
                {
                }
            }
            return null;
        }

        private class GenericNameValuePairComparer<T> : IComparer<KeyValuePair<string, T>>
            where T : IComparable
        {
            #region IComparer<KeyValuePair<string,T>> メンバー

            public int Compare(KeyValuePair<string, T> x, KeyValuePair<string, T> y)
            {
                return x.Value.CompareTo(y.Value);
            }

            #endregion
        }

        private static readonly EnumNameValuePairComparer Comparer = new EnumNameValuePairComparer();
        private class EnumNameValuePairComparer : IComparer<KeyValuePair<string, Enum>>
        {
            #region IComparer<KeyValuePair<string,Enum>> メンバー

            public int Compare(KeyValuePair<string, Enum> x, KeyValuePair<string, Enum> y)
            {
                return x.Value.CompareTo(y.Value);
            }

            #endregion
        }

        #region IListSource メンバー
        /// <summary>コレクションが <see cref="System.Collections.IList"/> オブジェクトのコレクションかどうかを示す値を取得します。</summary>
        /// <returns><c>true</c>。</returns>
        [Browsable(false)]
        public bool ContainsListCollection { get { return false; } }
        /// <summary>データ ソースに連結できる <see cref="System.Collections.IList"/> を取得します。</summary>
        /// <returns>オブジェクトからデータ ソースに連結できる <see cref="System.Collections.IList"/>。</returns>
        public IList GetList() { return (IList)_enumList; }
        #endregion
        #region ITypedList メンバー
        /// <summary>データをバインドするために使用される各項目のプロパティを表す <see cref="PropertyDescriptorCollection"/> オブジェクトを取得します。</summary>
        /// <param name="listAccessors">コレクション内で検索するバインド可能な <see cref="PropertyDescriptor"/> オブジェクトの配列。</param>
        /// <returns>データをバインドするために使用される各項目のプロパティを表す <see cref="PropertyDescriptorCollection"/> オブジェクト。</returns>
        public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) { return _properties; }
        /// <summary>リストの名前を取得します。</summary>
        /// <param name="listAccessors">リスト名を返す対象の <see cref="PropertyDescriptor"/> オブジェクトの配列。</param>
        /// <returns>リストの名前。</returns>
        public string GetListName(PropertyDescriptor[] listAccessors) { return null; }
        #endregion

    }
}